#pragma once

#include <cstdlib>
#include <stdio.h>
#include <string.h>

// Linux headers
#include <errno.h>   // Error integer and strerror() function
#include <fcntl.h>   // Contains file controls like O_RDWR
#include <termios.h> // Contains POSIX terminal control definitions
#include <unistd.h>  // write(), read(), close()

static inline int set_opt(int fd, int nSpeed, int nBits, char nEvent, int nStop) {
  struct termios newtio, oldtio;

  /*步骤一，设置字符大小*/
  bzero(&newtio, sizeof(newtio));
  newtio.c_cflag |= CLOCAL | CREAD;
  newtio.c_cflag &= ~CSIZE;

  /*设置数据位*/
  switch (nBits) {
  case 7:
    newtio.c_cflag |= CS7;
    break;
  case 8:
    newtio.c_cflag |= CS8;
    break;
  }

  /* 设置奇偶校验位 */
  switch (nEvent) {
  case 'O': // 奇数
    newtio.c_cflag |= PARENB;
    newtio.c_cflag |= PARODD;
    newtio.c_iflag |= (INPCK | ISTRIP);
    break;
  case 'E': // 偶数
    newtio.c_iflag |= (INPCK | ISTRIP);
    newtio.c_cflag |= PARENB;
    newtio.c_cflag &= ~PARODD;
    break;
  case 'N': // 无奇偶校验位
    newtio.c_cflag &= ~PARENB;
    break;
  }

  /*设置波特率*/
  switch (nSpeed) {
  case 2400:
    cfsetispeed(&newtio, B2400);
    cfsetospeed(&newtio, B2400);
    break;
  case 4800:
    cfsetispeed(&newtio, B4800);
    cfsetospeed(&newtio, B4800);
    break;
  case 9600:
    cfsetispeed(&newtio, B9600);
    cfsetospeed(&newtio, B9600);
    break;
  case 115200:
    cfsetispeed(&newtio, B115200);
    cfsetospeed(&newtio, B115200);
    break;
  case 460800:
    cfsetispeed(&newtio, B460800);
    cfsetospeed(&newtio, B460800);
    break;
  default:
    cfsetispeed(&newtio, B9600);
    cfsetospeed(&newtio, B9600);
    break;
  }

  /*设置停止位*/
  if (nStop == 1)
    newtio.c_cflag &= ~CSTOPB;
  else if (nStop == 2)
    newtio.c_cflag |= CSTOPB;

  /*设置等待时间和最小接收字符*/
  newtio.c_cc[VTIME] = 10;
  newtio.c_cc[VMIN] = 1;

  // 使用原始模式，如果以字符串形式接收下面两行可以去掉
  newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG | IEXTEN);
  newtio.c_oflag &= ~OPOST;

  /* Make raw */
  cfmakeraw(&newtio);

  /*处理未接收字符*/
  tcflush(fd, TCIFLUSH);

  /*激活新配置*/
  if ((tcsetattr(fd, TCSANOW, &newtio)) != 0) {
    perror("com set error");
    return -1;
  }

  return 0;
}

// https://www.programminghunter.com/article/97641003298/
static inline int uart_recv(int fd, char *rxBuff, int rxLen, int timeout) {
  fd_set fs_read;
  struct timeval time;

  time.tv_sec = timeout / 1000;         // set the rcv wait time
  time.tv_usec = timeout % 1000 * 1000; // 100000us = 0.1s

  FD_ZERO(&fs_read);    // 每次循环都要清空集合，否则不能检测描述符变化
  FD_SET(fd, &fs_read); // 添加描述符

  // 超时等待读变化，>0：就绪描述字的正数目， -1：出错， 0 ：超时
  auto fs_sel = select(fd + 1, &fs_read, NULL, NULL, &time);
  if (fs_sel) {
    auto len = read(fd, rxBuff, rxLen);
    return len;
  }

  return -1;
}

static inline int uart_send(int fd, char *txBuff, int txLen) {
  auto ret = write(fd, txBuff, txLen);
  if (ret == txLen)
    return ret;

  printf("write device error\n");
  tcflush(fd, TCOFLUSH);
  return -1;
}